#include "BtChannel.h"
#include "Network/BtSocket.h"
#include "Network/BtEndpoint.h"
#include  "Common/Defines.h"
#include "Common/Log.h"

namespace bOPRF {

	BtChannel::BtChannel(
		BtEndpoint& endpoint,
		std::string localName,
		std::string remoteName)
		:mEndpoint(endpoint),
		mLocalName(localName),
		mRemoteName(remoteName)
	{

	}

	BtChannel::~BtChannel()
	{
	}

	Endpoint & BtChannel::getEndpoint()
	{
		return *(Endpoint*)&mEndpoint;
	}

	std::string BtChannel::getName() const
	{
		return mLocalName;
	}

	void BtChannel::asyncSend(const void * buff, u64 size)
	{
		if (mSocket->mStopped)
			throw std::runtime_error("rt error at " LOCATION);

		BoostIOOperation op;

		op.mSize = (u32)size;
		op.mBuffs[1] = boost::asio::buffer((char*)buff, (u32)size);

		op.mType = BoostIOOperation::Type::SendData;

		mEndpoint.getIOService().dispatch(mSocket.get(), op);
	}

	void BtChannel::asyncSend(std::unique_ptr<ChannelBuffer> buff)
	{
		if (mSocket->mStopped)
			throw std::runtime_error("rt error at " LOCATION);

		BoostIOOperation op;

		op.mSize = (u32)buff->ChannelBufferSize();


		op.mBuffs[1] = boost::asio::buffer((char*)buff->ChannelBufferData(), (u32)buff->ChannelBufferSize());
		op.mType = BoostIOOperation::Type::SendData;

		op.mOther = buff.release();

		mEndpoint.getIOService().dispatch(mSocket.get(), op);
	}

	void BtChannel::send(const void * buff, u64 size)
	{
		if (mSocket->mStopped)
			throw std::runtime_error("rt error at " LOCATION);

		BoostIOOperation op;
		op.clear();

		op.mSize = (u32)size;
		op.mBuffs[1] = boost::asio::buffer((char*)buff, (u32)size);


		op.mType = BoostIOOperation::Type::SendData;

		std::promise<void> prom;
		op.mPromise = &prom;

		mEndpoint.getIOService().dispatch(mSocket.get(), op);

		prom.get_future().get();
	}

	std::future<void> BtChannel::asyncRecv(void * buff, u64 size)
	{
		if (mSocket->mStopped)
			throw std::runtime_error("rt error at " LOCATION);

		BoostIOOperation op;
		op.clear();

		op.mSize = (u32)size;
		op.mBuffs[1] = boost::asio::buffer((char*)buff, (u32)size);

		op.mType = BoostIOOperation::Type::RecvData;

		op.mOther = nullptr;

		op.mPromise = new std::promise<void>();
		auto future = op.mPromise->get_future();

		mEndpoint.getIOService().dispatch(mSocket.get(), op);

		return future;
	}

	std::future<void> BtChannel::asyncRecv(ChannelBuffer & mH)
	{
		if (mSocket->mStopped)
			throw std::runtime_error("rt error at " LOCATION);

		BoostIOOperation op;
		op.clear();


		op.mType = BoostIOOperation::Type::RecvData;

		op.mOther = &mH;

		op.mPromise = new std::promise<void>();
		auto future = op.mPromise->get_future();

		mEndpoint.getIOService().dispatch(mSocket.get(), op);

		return future;
	}

	void BtChannel::recv(void * dest, u64 length)
	{
		try {
			// schedule the recv.
			auto request = asyncRecv(dest, length);

			// block until the receive has been completed. 
			// Could throw if the length is wrong.
			request.get();
		}
		catch (BadReceiveBufferSize& bad)
		{
			Log::out << bad.mWhat << Log::endl;
			throw;
		}
	}

	void BtChannel::recv(ChannelBuffer & mH)
	{
		asyncRecv(mH).get();
	}

	bool BtChannel::opened()
	{
		return true;
	}
	void BtChannel::waitForOpen()
	{
		// async connect hasn't been implemented.
	}

	void BtChannel::close()
	{
		// indicate that no more messages should be queued and to fulfill
		// the mSocket->mDone* promised.
		mSocket->mStopped = true;


		BoostIOOperation closeRecv;
		closeRecv.mType = BoostIOOperation::Type::CloseRecv;
		std::promise<void> recvPromise;
		closeRecv.mPromise = &recvPromise;

		mEndpoint.getIOService().dispatch(mSocket.get(), closeRecv);

		BoostIOOperation closeSend;
		closeSend.mType = BoostIOOperation::Type::CloseSend;
		std::promise<void> sendPromise;
		closeSend.mPromise = &sendPromise;
		mEndpoint.getIOService().dispatch(mSocket.get(), closeSend);

		recvPromise.get_future().get();
		sendPromise.get_future().get();

		// ok, the send and recv queues are empty. Lets close the socket
		mSocket->mHandle.close();

		// lets de allocate ourselves in the endpoint.
		mEndpoint.removeChannel(getName());

		// WARNING: we are deallocated now. Do not touch any member variables.
	}


	std::string BtChannel::getRemoteName() const
	{
		return mRemoteName;
	}

	u64 BtChannel::getTotalDataSent() const
	{
		auto ret = (mSocket) ? (u64)mSocket->mTotalSentData : 0;
		mSocket->mTotalSentData = 0;
		return ret;
	}

	u64 BtChannel::getMaxOutstandingSendData() const
	{
		return (mSocket) ? (u64)mSocket->mMaxOutstandingSendData : 0;
	}
}